package com.archxs.commons.collections;

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

import com.archxs.commons.Variant;

/**
 * Proxies a Collection<String[]> as a collection of {@link Variant}s.
 */
public class VariantCollectionProxy implements VariantCollection {

    /**
     * Creates a new {@link VariantCollection} instance that is backed by a 
     * {@link Collection<String[]>}.
     * @param  proxiedCollection The actual datastore that is proxied by the returned 
     *                           {@link VariantCollection} object.
     * @return {@link VariantCollection} instance that operates on the provided
     *         collection instance.
     */
    public static VariantCollection proxyInstance(final Collection<String[]> proxiedCollection) {
        return proxyInstance(null, proxiedCollection);
    }
    
    /**
     * Creates a new {@link VariantCollection} instance that is backed by a 
     * {@link Collection<String[]>}.
     * @param  type              The Variant type to use.
     * @param  proxiedCollection The actual datastore that is proxied by the returned 
     *                           {@link VariantCollection} object.
     * @return {@link VariantCollection} instance that operates on the provided
     *         collection instance.
     */
    public static VariantCollection proxyInstance(final Class<? extends Variant> type, final Collection<String[]> proxiedCollection) {
        return new VariantCollectionProxy(type, proxiedCollection);
    }
    
    // +-----------------------------------------------------------------------+
    // | Collection
    // +-----------------------------------------------------------------------+
    @Override
    public int size() {
        return m_inner.size();
    }

    @Override
    public boolean isEmpty() {
        return m_inner.isEmpty();
    }

    @Override
    public boolean contains(final Object value) {
        if (value instanceof Variant) {
            return contains(m_inner, (Variant)value);
        }
        return false;
    }

    @Override
    public Iterator<Variant> iterator() {
        return new ProxyIterator(m_inner.iterator());
    }

    @Override
    public Object[] toArray() {
        return toArray(ARRAY_CASTER);
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T[] toArray(final T[] a) {
        // Don't test a since Java throws NPEs from their collections when this
        // argument is null.
        final Class<?> componentType;
        componentType = a.getClass().getComponentType();
        final T[] innerValues;
        if (componentType == String[].class) {
            innerValues = m_inner.toArray(a);
            if (a == innerValues) {
                return (T[])innerValues;
            }
        } else {
            innerValues = (T[])m_inner.toArray();
        }
        final Object[] array = (Object[]) Array.newInstance(componentType, innerValues.length);
        for(int i = 0; i < innerValues.length; ++i) {
            final Variant v = Variant.valueOf(m_variantType, (String[])innerValues[i]);
            if (Variant.class.isAssignableFrom(componentType)) {
                array[i] = v;
            } else {
                array[i] = Variant.dynamic_cast(componentType, v);
            }
        }
        return (T[])array;
    }

    @Override
    public boolean add(final Variant e) {
        return m_inner.add(e.getArray(ARRAY_CASTER));
    }

    @Override
    public boolean remove(final Object o) {
        if (o instanceof Variant) {
            final String[] valueToRemove = ((Variant)o).getArray(ARRAY_CASTER);
            final Iterator<String[]> i = m_inner.iterator();
            while(i.hasNext()) {
                if (Arrays.equals(i.next(), valueToRemove)) {
                    i.remove();
                    return true;
                }
            }
        }
        return false;
    }

    @Override
    public boolean containsAll(final Collection<?> values) {
        if (null != values && values.size() > 0) {
            boolean foundVariant = false;
            for(final Object value : values) {
                // TODO: WOW, On^3 runtime! That's total trash. Fixme!
                if (!contains(value)) {
                    return false;
                } else {
                    foundVariant = true;
                }
            }
            return foundVariant;
        }
        return false;
    }

    @Override
    public boolean addAll(final Collection<? extends Variant> c) {
        boolean changed = false;
        if (null != c) {
            for(final Variant v : c) {
                changed = add(v);
            }
        }
        return changed;
    }

    @Override
    public boolean removeAll(final Collection<?> c) {
        boolean changed = false;
        if (null != c) {
            for(final Object v : c) {
                changed = remove(v);
            }
        }
        return changed;
    }

    @Override
    public boolean retainAll(final Collection<?> c) {
        boolean modified = false;
        final Iterator<String[]> innerIterator = m_inner.iterator();
        while(innerIterator.hasNext()) {
            if(!c.contains(Variant.valueOf(innerIterator.next()))) {
                innerIterator.remove();
                modified = true;
            }
        }
        return modified;
    }

    @Override
    public void clear() {
        m_inner.clear();
    }

    // +-----------------------------------------------------------------------+
    // | DEFAULT
    // +-----------------------------------------------------------------------+
    /**
     * Implementation of the "contains" algorithm to share amongst members of this
     * package.
     * @param  values   The values collection to test for membership.
     * @param  value    The variant to test for.
     * @return <code>true</code> if the values collection contains the provided
     *         value else <code>false</code>.
     * @throws NullPointerException If either argument is <code>null</code>.
     */
    static boolean contains(final Collection<String[]> values, final Variant value) {
        final String[] stringValue = value.getArray(new String[value.getArrayLength()]);
        //TODO: ugh, On^2 time. Find a better runtime for this
        for(final String[] valueInCollection : values) {
            if (Arrays.equals(valueInCollection, stringValue)) {
                return true;
            }
        }
        return false;
    }

    // +-----------------------------------------------------------------------+
    // | PRIVATE
    // +-----------------------------------------------------------------------+
    /**
     * Iterator type that proxies a "real" iterator into our inner collection.
     */
    private static class ProxyIterator implements Iterator<Variant> {

        private final Iterator<String[]> m_innerIterator;
        
        private ProxyIterator(final Iterator<String[]> innerIterator) {
            m_innerIterator = innerIterator;
        }
        
        // +-------------------------------------------------------------------+
        // | ITERATOR
        // +-------------------------------------------------------------------+
        @Override
        public boolean hasNext() {
            return m_innerIterator.hasNext();
        }

        @Override
        public Variant next() {
            return Variant.valueOf(m_innerIterator.next());
        }

        @Override
        public void remove() {
            m_innerIterator.remove();
        }
    };
    
    VariantCollectionProxy(final Class<? extends Variant> type, final Collection<String[]> innerCollection) {
        if (null == innerCollection) {
            throw new IllegalArgumentException("No collection data passed into proxy constructor.");
        }
        if (null == type) {
            m_variantType = Variant.getVariantType();
        } else {
            m_variantType = type;
        }
        m_inner = innerCollection;
    }

    private final Collection<String[]> m_inner;
    private final Class<? extends Variant> m_variantType;
    
    /** Typed array used for {@link Variant#getArray(T[])} */
    private final static String[] ARRAY_CASTER = new String[]{};
}
